#!/usr/bin/python
# Copyright 2008, 2009 Dominic Spill, Michael Ossmann
"""
Bluetooth monitoring utility.
Receives samples from radio, file (as created by osmo_sdr), or standard input.
If LAP is unspecified, LAP detection mode is enabled.
If LAP is specified without UAP, UAP detection mode is enabled.
"""

from gnuradio import gr, blocks

import gr_bluetooth
from gnuradio.eng_option import eng_option
from optparse import OptionParser

class my_top_block(gr.top_block):

	def __init__(self):
		gr.top_block.__init__(self)

		usage="%prog: [options]"
		parser = OptionParser(option_class=eng_option, usage=usage)
		parser.add_option("-a", "--args", type="string", default="",
						help="UHD device address args , [default=%default]")
		parser.add_option("", "--spec", type="string", default=None,
						help="Subdevice of UHD device where appropriate")
		parser.add_option("-A", "--antenna", type="string", default=None,
						help="select Rx Antenna where appropriate")
		parser.add_option("-f", "--freq", type="eng_float", default=2.476e9,
						help="set frequency to FREQ", metavar="FREQ")
		parser.add_option("-g", "--gain", type="eng_float", default=0.0,
						help="set gain in dB (default is midpoint)")
		parser.add_option("-N", "--nsamples", type="eng_float", default=None,
						help="number of samples to collect [default=+inf]")
		parser.add_option("-S", "--sniff", action="store_true", default=False,
						help="all-piconet sniffer")
		parser.add_option("", "--aliased", action="store_true", default=False,
						help="using a particular aliasing receiver implementation")
		parser.add_option("-c", "--ddc", type="string", default="0",
						help="comma separated list of ddc frequencies (default=0)")
		parser.add_option("-d", "--decim", type="int", default=32,
						help="set fgpa decimation rate to DECIM (default=32)") 
		parser.add_option("-i", "--input-file", type="string", default=None,
						help="use named input file instead of USRP")
		parser.add_option("-l", "--lap", type="string", default=None,
						help="LAP of the master device")
		parser.add_option("-n", "--channel", type="int", default=None,
						help="channel number for hop reversal (0-78) (default=None)") 
		parser.add_option("-p", "--hop", action="store_true", default=False,
						help="reverse hopping sequence to determine master clock")
		parser.add_option("-r", "--sample-rate", type="eng_float", default=None,
						  help="sample rate of input (default: use DECIM)")
		parser.add_option("-s", "--input-shorts", action="store_true", default=False,
						help="input interleaved shorts instead of complex floats")
		parser.add_option("-t", "--snr", type="eng_float", default=10.0,
						help="SNR squelch threshold in dB (default=10.0)")
		parser.add_option("-w","--wireshark", action="store_true", default=False,
						help="direct output to a tun interface")

		(options, args) = parser.parse_args ()
		if len(args) != 0:
			parser.print_help()
			raise SystemExit, 1

		# Bluetooth operates at 1 million symbols per second
		symbol_rate = 1e6

		# the demodulator needs at least two samples per symbol
		min_samples_per_symbol = 2
		min_sample_rate = symbol_rate * min_samples_per_symbol

		# use options.sample_rate unless not provided by user
		if options.sample_rate is None:
			raise ValueError, "Sample rate must be provided"

		# make sure we have a high enough sample rate
		if options.sample_rate < min_sample_rate:
			raise ValueError, "Sample rate (%d) below minimum (%d)\n" % (options.sample_rate, min_sample_rate)

		if options.input_shorts:
			input_size = gr.sizeof_short
		else:
			input_size = gr.sizeof_gr_complex

		stages = []

		# select input source
		if options.input_file is None:
			try:
				import osmosdr
			except:
				print 'Unable to import osmosdr module, please make sure it was installed and try again'
				raise SystemExit, 1
			try:
				src = osmosdr.source(args=options.args)
				src.set_sample_rate(options.sample_rate)
				src.set_freq_corr(0, 0)
				src.set_dc_offset_mode(0, 0)
				src.set_iq_balance_mode(0, 0)
				src.set_gain_mode(False, 0)
				src.set_gain(options.gain, 0)
				src.set_if_gain(20, 0)
				src.set_bb_gain(20, 0)
				src.set_antenna("", 0)
				src.set_bandwidth(0, 0)
				#print "Using RX board id 0x%04X" % (src.daughterboard_id(),)
				#src.set_decim(options.decim)
				r = src.set_center_freq(options.freq, 0)
			except:
				raise
				print 'Error accessing USRP, ensure it is plugged in and switched on and try again'
				parser.print_help()
				raise SystemExit, 1
			if r == None:
					raise SystemExit, "Failed to set USRP frequency"
			if options.gain is None:
					# if no gain was specified, use the mid-point in dB
					g = src.gain_range()
					options.gain = float(g[0]+g[1])/2
			src.set_gain(options.gain)
		elif options.input_file == '-':
			# input from stdin
			src = blocks.file_descriptor_source(input_size, 0)
		else:
			# input from file
			src = blocks.file_source(input_size, options.input_file)

		# stage 1: limit input to desired number of samples
		if options.nsamples:
			head = blocks.head(input_size, int(options.nsamples))
			self.connect(src, head)
			src = head
	
		# stage 2: convert input from shorts if necessary
		if options.input_shorts:
			s2c = blocks.interleaved_short_to_complex()
			self.connect(src, s2c)
			src = s2c

		# bluetooth decoding
		if options.sniff:
			# decode all packets from all piconets on all channels,
			# discovering UAPs and clocks as necessary
			dst = gr_bluetooth.multi_sniffer(options.sample_rate, options.freq,
											 options.snr, options.wireshark)
		elif options.lap is None:
			# print out LAP for every frame detected
			dst = gr_bluetooth.multi_LAP(options.sample_rate, options.freq,
										 options.snr)
		elif options.hop:
			# determine UAP and then master clock from hopping sequence
			dst = gr_bluetooth.multi_hopper(options.sample_rate, options.freq,
											options.snr, int(options.lap, 16),
											options.aliased, options.wireshark)
		else:
			# determine UAP from frames matching the user-specified LAP
			dst = gr_bluetooth.multi_UAP(options.sample_rate, options.freq,
										 options.snr, int(options.lap, 16))
		self.connect(src, dst)

if __name__ == '__main__':
	#raw_input("Press return to continue...")
	try:
		my_top_block().run()
	except KeyboardInterrupt:
		pass
